#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <alsa/asoundlib.h>
/**
 * Desc: 利用alsa驱动播放wav文件的简单播放器啊
 * Auth: 张宇飞
 * Date: 2015-09-18    
 */

typedef struct {
	char		ID[4];
	int		size;
}Chunk_t;

typedef struct {
	Chunk_t		ch;
	char		s[4];
}WaveHead_t;

typedef struct {
	int16_t		formatTag;	// 编码方式
	int16_t		channels;	// 声道数目
	int		samplesPerSec;	// 采样频率
	int		avgBytesPerSec;	// 每秒所需字节数 samplesPerSec * bitsPerSample * channels / 8
	int16_t		blockAligin;	// 每个采样需要的字节数 channels * bitsPerSample / 8
	int16_t		bitsPerSample;	// 每个采样需要的bit数.8 ,16 etc
	int		buffersize;	// 需要开辟的缓冲区大小
}WaveFormat_t;

typedef struct {
	snd_pcm_format_t	format;	// 编码格式
	unsigned int		channel;// 声道数量
	unsigned int		rate;	// 采样频率
	snd_pcm_uframes_t	frames; // 每个周期多少帧
	unsigned int		frameN;	// 一共有多少帧
	void*			buf;
}AudioBuffer_t;

/**
 * @brief fact chunk, cue points, playlist, assoc-data-list
 */
static void show_extra_chunk_info(FILE* fp)
{
	// fact, cue, playlist存在于cue文件中。我需要检测是否存在assoc-data-list
	Chunk_t tmp;
	if (fread(&tmp, sizeof(tmp), 1, fp) < 1)
		return;
	if (memcmp(tmp.ID, "LIST", 4) == 0) {
		fseek(fp, tmp.size, SEEK_CUR);
	} else {
		fseek(fp, -sizeof(tmp), SEEK_CUR);
	}
}

/**
 * @brief 获取数据段的大小
 */
static int get_data_chunk_info(FILE* fp, WaveFormat_t* pf)
{
	Chunk_t dc;
	if (fread(&dc, sizeof(dc), 1, fp) < 1) {
		fprintf(stderr, "读取文件data段失败\n");
		return -1;
	}
	if (memcmp(dc.ID, "data", 4) != 0) {
		fprintf(stderr, "不存在DATA数据段\n");
		return -1;
	}
	pf->buffersize = dc.size;
	return 0;
}

/**
 * @brief 获取wave的格式信息
 * @return 返回0表示成功
 */
static int get_wave_format_info(FILE* fp, WaveFormat_t* out)
{
	// 校验文件类型
	WaveHead_t head;
	if (fread(&head, sizeof(WaveHead_t), 1, fp) < 1) {
		fprintf(stderr, "读取文件失败\n");
		return -1;
	}
	if ((memcmp(head.ch.ID, "RIFF", 4) != 0) || (memcmp(head.s, "WAVE", 4) != 0)) {
		fprintf(stderr, "不是标准的wave文件\n");
		return -1;
	}
	// 检验文件完整性
	fseek(fp, 0, SEEK_END);
	long fs = ftell(fp);
	fseek(fp, sizeof(WaveHead_t), SEEK_SET);
	if (fs != (head.ch.size + sizeof(Chunk_t))) {
		fprintf(stderr, "文件出现破损，数据不完整\n");
		return -1;
	}
	// 读取fmt段
	if ((fread(&head.ch, sizeof(Chunk_t), 1, fp) < 1) || (memcmp(head.ch.ID, "fmt ", 4) != 0)) {
		fprintf(stderr, "读取fmt段出现错误\n");
		return -1;
	}
	if ((16 != head.ch.size)) {
		fprintf(stderr, "fmt段大小为:%d, PCM为16.其它格式不支持\n", head.ch.size);
		return -1;
	}
	if (fread(out, 16, 1, fp) < 1) {
		fprintf(stderr, "读取fmt段信息失败\n");
		return -1;
	}
	if (out->formatTag != 1) {
		fprintf(stderr, "只支持PCM模式的文件, 当前文件的格式: %d\n", out->formatTag);
		return -1;
	}
	if (out->bitsPerSample != 16 && out->bitsPerSample != 8) {
		fprintf(stderr, "当前文件帧大小:%d.而程序只支持8和16bits模式\n", out->bitsPerSample);
		return -1;
	}
	printf("wave info:\n");
	printf("\tformat = %d\n", out->formatTag);
	printf("\tsamplesPerSec = %d\n", out->samplesPerSec);
	printf("\tchannels = %d\n", out->channels);
	printf("\tbitsperSample = %d\n", out->bitsPerSample);
	printf("\tblockAlign = %d\n", out->blockAligin);
	printf("\tavgBytesPerSec = %d\n", out->avgBytesPerSec);
	show_extra_chunk_info(fp);
	return get_data_chunk_info(fp, out);
}

static void free_audio_buffer(AudioBuffer_t* pbuf)
{
	if (pbuf->buf)
		free(pbuf->buf);
	free(pbuf);
}

static AudioBuffer_t* load2audiobuffer(FILE* fp, const WaveFormat_t* pinfo)
{
	if (pinfo->buffersize < 1)
		return NULL;
	AudioBuffer_t* ret = (AudioBuffer_t*)malloc(sizeof(AudioBuffer_t));
	if (!ret) {
		fprintf(stderr, "创建AudioBuffe对象失败\n");
		return NULL;
	}
	ret->buf = malloc(pinfo->buffersize);
	if (!ret->buf) {
		fprintf(stderr, "创建音频缓冲失败\n");
		free(ret);
		return NULL;
	}
	printf("正在缓冲文件……\n");
	// 加载文件数据到缓冲
	if (fread(ret->buf, 1, pinfo->buffersize, fp) != pinfo->buffersize) {
		fprintf(stderr, "缓冲文件失败\n");
		free_audio_buffer(ret);
		return NULL;
	} else {
		printf("完成文件缓冲\n");
	}
	// 转换音频格式
	ret->rate = pinfo->samplesPerSec;
	ret->channel = pinfo->channels;
	ret->frames = pinfo->blockAligin * 50; // 配置一个周期处理多少帧
	ret->format = (16 == pinfo->bitsPerSample) ? SND_PCM_FORMAT_S16_LE : SND_PCM_FORMAT_U8;
	ret->frameN = pinfo->buffersize * 8 / pinfo->bitsPerSample / pinfo->channels;
	return ret;
}

static void err_handle(const char* msg, int rc)
{
	if (rc < 0) {
		fprintf(stderr, "%s: %s\n", msg, snd_strerror(rc));
		exit(1);
	}
}

/**
 * @brief 播放音频
 * @think 如果在这里加入线程，就能实现异步控制
 */
static void play_sound(AudioBuffer_t* pbuf)
{
	// 配置硬件
	long loops;
	int rc, size, dir;
	snd_pcm_t* handle;
	snd_pcm_hw_params_t* params;
	unsigned int val;
	snd_pcm_uframes_t frames;
	char* buffer;

	// 打开设备
	rc = snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0);
	err_handle("unable to pen pcm device", rc);

	// 创建硬件参数内存
	snd_pcm_hw_params_alloca(&params);

	// 配置pcm参数
	snd_pcm_hw_params_any(handle, params);
	if (2 == pbuf->channel)
		snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);
	else 
		snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_NONINTERLEAVED);

	snd_pcm_hw_params_set_format(handle, params, pbuf->format);
	snd_pcm_hw_params_set_channels(handle, params, pbuf->channel);
	snd_pcm_hw_params_set_rate_near(handle, params, &pbuf->rate, &dir);
	snd_pcm_hw_params_set_period_size_near(handle, params, &pbuf->frames, &dir);
	rc = snd_pcm_hw_params(handle, params);
	err_handle("unable to set hw parameters", rc);

	// 获取一个周期能容纳多少帧
	snd_pcm_hw_params_get_period_size(params, &frames, &dir);
	size = frames * 4;	//一个周期的内存大小

	// 计算播放循环次数以及播放时间
	snd_pcm_hw_params_get_period_time(params, &val, &dir);
	loops = pbuf->frameN / frames;
	if (pbuf->frameN % frames)
		++loops;
	rc = val * loops;
	dir = (rc / 1000000) % 60;
	printf("文件预计播放 %d分 %d秒\n", rc / 60000000, dir);
	buffer = (char*)pbuf->buf;
	while(loops --> 0)
	{
		rc = snd_pcm_writei(handle, buffer, frames); // 写入声卡
		if (rc == -EPIPE) {
			// EPIPE means underrun
			fprintf(stderr, "underrun occurred\n");
			snd_pcm_prepare(handle);
		} else if (rc < 0) {
			fprintf(stderr, "error from writei: %s\n", snd_strerror(rc));
		} else if (rc != (int)frames) {
			fprintf(stderr, "short write, write %d frames\n", rc);
		}
		buffer += size;
	}
	snd_pcm_drain(handle);
	snd_pcm_close(handle);
}

int main(int argc, char *argv[])
{
	if (argc < 2) {
		printf("usage: %s <wave file path>\n", argv[0]);
		return 0;
	}
	FILE* fp = fopen(argv[1], "rb");
	if (!fp) {
		printf("打开文件%s失败\n", argv[1]);
		goto do_err;
	}
	WaveFormat_t f;
	if (get_wave_format_info(fp, &f) < 0)
		goto do_err;
	AudioBuffer_t* pbuf = load2audiobuffer(fp, &f);		
	fclose(fp);
	if (!pbuf)
		return -1;
	play_sound(pbuf);
	free_audio_buffer(pbuf);
	return 0;
do_err:
	fclose(fp);
	return 0;
}
